import os
import threading
import time

import gobject
gobject.threads_init()
import pygst
pygst.require("0.10")
import gst
import gtk

import rsvg
import goocanvas
import cairo

from groundwork.persist import (Class,
                                String,
                                Float,
                                Integer,
                                Reference,
                                BackReference,
                                ForeignReference)

from groundwork.model import (Model,
                              PropertyView,
                              TreeViewProxy,
                              View,
                              StringTraits,
                              PathTraits,
                              FloatTraits,
                              AngleTraits,
                              IntTraits,
                              ViewManager)

from groundwork.editor import (Base, 
                               HistoryAction,
                               Editor,
                               config_storage,
                               storage)
                               
from groundwork.ui import DragSensor

import tinkerlib

###############################################################################

class PNode(Base):
    __storage__ = storage
    _parent_ = 'project'
    #_image_ = 'shape.png'
    
    project = Reference('Project')
    x = Float(traits=FloatTraits(
            label="X",
            default = 0,
            order=10,
        ))
    y = Float(traits=FloatTraits(
            label="Y",
            default = 0,
            order=11,
        ))
        
    def get_name(self):
        return self.op.uri
    
    def __init__(self):
        pass

###############################################################################

class OpFactory(object):
    def __init__(self):
        # uri -> class
        self.classes = {}
        self.pnodes = {}
        
    def register_class(self, cls):
        self.classes[cls.uri] = cls
        # create PNode class<
        interface = dict(
            op = cls
        )
        pnode = type("PNode(%s)" % cls.uri, (PNode,), interface)
        storage.register_class(pnode)
        self.pnodes[cls] = pnode
    
    def register(self, oplist):
        for cls in oplist:
            if cls.uri in self.classes:
                print "warning: multiple ops for URI %r" % cls.uri
            self.register_class(cls)
            
    def get_completion_entries(self):
        return self.classes.keys()

factory = OpFactory()

factory.register(tinkerlib.__ops__)

###############################################################################

class Project(Base):
    __storage__ = storage
    #_image_ = 'project.png'
    
    name = String(traits=StringTraits(
            label="Name",
            order=0,
        ))

    # back references
    nodes = BackReference(PNode, attribute='project')

    def get_name(self):
        if config.project is self:
            return '<b>%s</b>' % self.name
        return self.name

    def get_parent(self):
        return None

    def get_children(self):
        return self.nodes.all()
        
    def __init__(self, name='Project'):
        Base.__init__(self, name)
        
    def new_node(self, uri):
        cls = factory.classes[uri]
        node = factory.pnodes[cls]()
        node.project = self
        storage.add(node)
        return node
        
    def command(self, cmd):
        with storage.edit("New %s" % cmd):
            node = self.new_node(cmd)

###############################################################################

def new_project():
    with storage.edit("New Project"):
        project = Project()
        storage.add(project)

###############################################################################

BASEPATH = os.path.expanduser('~/.tinkerama')
if not os.path.isdir(BASEPATH):
    os.makedirs(BASEPATH)

CONFIG_PATH = os.path.join(BASEPATH, 'config.db')
DB_PATH = os.path.join(BASEPATH, 'tinkerama.db')

###############################################################################

class Config(Class):
    __storage__ = config_storage
    
    project = ForeignReference(Project)

config = config_storage.open(CONFIG_PATH,Config)

###############################################################################

class TinkeramaModel(Model):
    __root__ = Project

###############################################################################

class TinkeramaViewManager(ViewManager):
    __root__ = Project

    def __init__(self):
        ViewManager.__init__(self)
        config_storage.connect('member-changed', self.on_cfg_member_changed)
        self.restore_views()
        
    def refresh(self):
        pass
        
    def on_cfg_member_changed(self, instance, member):
        if instance is config and member is Config.project:
            self.restore_views()
            
    def on_members_changed(self, instance):
        if instance is config.project:
            self.refresh()
        ViewManager.on_members_changed(self, instance)
        
    def restore_views(self):
        self.destroy_views()
        if not config.project:
            return
        ViewManager.restore_views(self, config.project.get_children())
        self.refresh()

###############################################################################

class ProjectView(goocanvas.Canvas, TinkeramaViewManager):

    cursor = gtk.gdk.Cursor(gtk.gdk.ARROW)
            
    CLASS_MAP = {
        #Shape : ShapeView,
        #DSPItem : DSPView,
        #ConnectionItem : ConnectionView,
    }
    
    def __init__(self):
        goocanvas.Canvas.__init__(self)
        
        completion_model = gtk.ListStore(str)
        for cmd in factory.get_completion_entries():
            completion_model.append([cmd])
        self.completion_model = completion_model
        
        self.pan_x,self.pan_y = 0.0,0.0
        self.drag_x,self.drag_y = 0.0,0.0
        self.zoom = 0.0
        self.set_property('automatic-bounds', True)
        self.connect('scroll-event', self.on_scroll_event)
        
        self.sensor1 = DragSensor(self)
        self.sensor1.connect('button-down', self.on_button_down)
        
        self.sensor2 = DragSensor(self,button=2)
        self.sensor2.connect('dragging', self.on_dragging)
        self.sensor2.connect('begin-drag', self.on_begin_drag)
        self.sensor2.connect('end-drag', self.on_end_drag)
        
        TinkeramaViewManager.__init__(self)
        
    def clear_input(self, widget, event, inp):
        inp.remove()
        
    def activate_input(self, widget, inp):
        cmd = widget.get_text()
        inp.remove()
        config.project.command(cmd)
        
    def on_new_op(self, x, y):
        root = self.get_root_item()
        x,y = self.convert_to_item_space(root, x,y)
        
        entry = gtk.Entry()
        completion = gtk.EntryCompletion()
        entry.set_completion(completion)
        completion.set_model(self.completion_model)
        completion.set_text_column(0)
        completion.set_inline_completion(True)
        completion.set_inline_selection(True)
        completion.set_popup_completion(True)
        
        inp = goocanvas.Widget(parent=root,
            x=x, y=y, widget=entry)
        entry.grab_focus()
        entry.connect('focus-out-event', self.clear_input, inp)
        entry.connect('activate', self.activate_input, inp)
        
    def on_button_down(self, widget, event):
        if event.clicks == 2:
            self.on_new_op(event.x, event.y)
        else:
            gtk.Widget.grab_focus(self)
        
    def on_begin_drag(self, widget, event):
        self.drag_x,self.drag_y = event.dx,event.dy
        self.refresh()
        
    def on_dragging(self, widget, event):
        self.drag_x = event.dx
        self.drag_y = event.dy
        self.refresh()

    def on_end_drag(self, widget, event):
        self.pan_x += self.drag_x
        self.pan_y += self.drag_y
        self.drag_x = 0.0
        self.drag_y = 0.0
        self.refresh()

    def on_scroll_event(self, widget, event):
        if event.direction == gtk.gdk.SCROLL_UP:
            step = 0.1
        elif event.direction == gtk.gdk.SCROLL_DOWN:
            step = -0.1
        self.zoom += step
        self.refresh()
        return True
        
    def refresh(self):
        root = self.get_root_item()
        mtx = cairo.Matrix()
        mtx.translate(self.pan_x+self.drag_x, self.pan_y+self.drag_y)
        zoom = 2**self.zoom
        mtx.scale(zoom, zoom)
        root.set_transform(mtx)
        
    def set_cursor(self, cursor_id):
        if cursor_id is None:
            self.get_window().set_cursor(self.cursor)
        else:
            self.get_window().set_cursor(gtk.gdk.Cursor(cursor_id))

###############################################################################

class Tinkerama(Editor):
    _context_menus = {
        None : 'generic_menu',
        Project : 'project_menu',
    }
    
    GLADE_PATH = 'tinkerama.glade'
    CLIPBOARD_TARGET = 'TINKERAMA_ITEMS'
    APP_TITLE = 'Tinkerama'
    STORAGE_PATH = DB_PATH
    MODEL_CLASS = TinkeramaModel
    
    def __init__(self):
        Editor.__init__(self)

        self.history_action = HistoryAction(self.undo_action, self.redo_action)
        
        self.view = ProjectView()
        self.viewscroll.add(self.view)
        
        #self.connect_action(self.play_action, 'F5')
        
        self.update_title()
        
    def update_title(self):
        if config.project:
            self.set_title(config.project.name)
        else:
            self.set_title(None)

    def set_project(self, project):
        config.project = project
        self.update_title()

    def finalize_init(self):
        Editor.finalize_init(self)
        # center view
        vadj = self.viewscroll.get_vadjustment()
        vadj.set_value((vadj.lower + vadj.upper - vadj.page_size)/2)
        hadj = self.viewscroll.get_hadjustment()
        hadj.set_value((hadj.lower + hadj.upper - hadj.page_size)/2)
        
    def on_aboutmenuitem_activate(self, menuitem):
        self.aboutdialog.run()
        self.aboutdialog.hide()

    def on_itemtree_row_activated(self, widget, path, column):
        store = widget.get_model()
        iter = store.get_iter(path)
        item = store.get_value(iter, 0)
        if isinstance(item, Project):
            self.set_project(item)
        
    def on_itemtree_button_press_event(self, widget, event):
        if event.button == 3:
            context = widget.get_path_at_pos(int(event.x), int(event.y))
            if context:
                store = widget.get_model()
                context = store.get_value(store.get_iter(context[0]), 0)
                selection = self.itemtree.get_selection()
                if context in selection:
                    pass
                else:
                    self.itemtree.select_items([context])
            else:
                self.itemtree.select_items([])
            self.popup_menu(event)
            return True
        
    def on_new_project_activate(self, menuitem):
        new_project()
        
    #def on_new_shape_activate(self, action):
    #    with storage.edit("New Shape"):
    #        for project in self.itemtree.get_selection():
    #            project.new_shape()

###############################################################################

if __name__ == '__main__':
    app = Tinkerama()
    app.main()
